﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace NavFob
{ 

    public class NAV2013BLOBReader
    {
        public static string ReadFromDB(string server,string database, string username, string pass, string ID, int type = 1)
        {

            //string sqlConnectString = string.Format("server={0};Trusted_Connection=yes;database={1};User ID={2};Password={3}", server, database,username,pass);
            string sqlConnectString = string.Format("server={0};database={1};User ID={2};Password={3}", server, database, username, pass);
            
            using (SqlConnection connection = new SqlConnection(sqlConnectString))
            {
                connection.Open();
                //SqlCommand command = new SqlCommand(string.Format("select [User Code] from [Object Metadata] where [Object Type] = {1} AND [Object ID] = {0}", ID, type), connection);
                SqlCommand command = new SqlCommand(string.Format("select [Metadata],[User Code] from [Object Metadata] where [Object Type] = {1} AND [Object ID] = {0}", ID, type), connection);

                SqlDataReader reader = command.ExecuteReader();

                if (reader.Read())
                {

                    var ms = new MemoryStream();
                    if (GetDecompressedStream2013(reader, true, ref ms) == 1)
                    {
                        ms.Position = 0;
                        StreamReader Sreader = new StreamReader(ms);
                        string text = Sreader.ReadToEnd();
                        return text;
                    }
                }
            }
            return string.Empty;
        }
        public static string ReadFromHexaString(string strHexa)
        {

            var ms = new MemoryStream();
            if (GetDecompressedStream2013(strHexa, true, ref ms) == 1)
            {
                ms.Position = 0;
                StreamReader Sreader = new StreamReader(ms);
                string text = Sreader.ReadToEnd();
                return text;
            }
             return string.Empty;
        }
        public static int GetDecompressedStream2013(SqlDataReader reader, bool decompress, ref MemoryStream ResultStream)
        {
            int fieldIndex = 0;
            int num2 = reader.IsDBNull(fieldIndex) ? 0 : ((int)reader.GetBytes(fieldIndex, 0L, null, 0, 0));
            if (num2 > 0)
            {
                using (Stream stream = new BlobReaderStream(reader, fieldIndex, num2))
                {
                    if (decompress)
                    {
                        byte[] array = new byte[4];
                        long num3 = (long)stream.Read(array, 0, 4);
                        if (num3 < 4L || !BlobMagicOk(array))
                        {
                            int result = 22926086;
                            return result;
                        }

                        using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress))
                        {
                            deflateStream.CopyTo(ResultStream);
                        }
                    }
                    else
                        stream.CopyTo(ResultStream);
                    return 1;
                }
            }
            return 0;
        }
        public static byte[] StringToByteArray(string hex)
        {
            return Enumerable.Range(0, hex.Length)
                             .Where(x => x % 2 == 0)
                             .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                             .ToArray();
        }
        public static int GetDecompressedStream2013(string strHexa, bool decompress, ref MemoryStream ResultStream)
        {
            int fieldIndex = 0;
            int num2 = strHexa.Length/2;
            if (num2 > 0)
            {
                byte[] byteHexa = StringToByteArray(strHexa);
                using (Stream stream = new MemoryStream(byteHexa))
                {
                    if (decompress)
                    {
                        byte[] array = new byte[4];
                        long num3 = (long)stream.Read(array, 0, 4);
                        if (num3 < 4L || !BlobMagicOk(array))
                        {
                            int result = 22926086;
                            return result;
                        }                        
                        using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress))
                        {
                            deflateStream.CopyTo(ResultStream);
                        }
                    }
                    else
                        stream.CopyTo(ResultStream);
                    return 1;
                }
            }
            return 0;
        }
        private static bool BlobMagicOk(byte[] checkMagic)
        {
            for (int i = 0; i < 4; i++)
            {
                if (checkMagic[i] != BlobMagic[i])
                {
                    return false;
                }
            }
            return true;
        }

        private static byte[] BlobMagic = new byte[]
        {
            2,
            69,
            125,
            91
        };
    }

    /// <summary>
    /// Represents a read-only stream that reads data directly from an open SqlDataReader
    /// for a single column. The SqlCommand that was executed to obtain the SqlDataReader
    /// must be executed with the `CommandBehavior.SequentialAccess` option for best results,
    /// otherwise the entire row will be read in to memory at once thus defeating the
    /// purpose of streaming content directly from the database that may be larger than
    /// available memory.
    /// </summary>
    public sealed class BlobReaderStream : Stream
    {
        private SqlDataReader dr;
        private string strHexa;
        private int ordinal;
        private long? length;
        private long position;

        /// <summary>
        /// Creates a stream that reads over the given SqlDataReader with the current column ordinal.
        /// </summary>
        /// <param name="dr"></param>
        /// <param name="ordinal"></param>
        /// <param name="length">Optional parameter used to supply the Length property with.
        /// If null (the default), the Length property will throw an exception if it is accessed.</param>
        public BlobReaderStream(SqlDataReader dr, int ordinal, long? length = null)
        {
            this.dr = dr;
            this.ordinal = ordinal;
            this.length = length;

            this.position = 0;
        }
        public BlobReaderStream(string hexa, int ordinal, long? length = null)
        {
            this.strHexa = hexa;
            this.ordinal = ordinal;
            this.length = length;

            this.position = 0;
        }

        /// <summary>
        /// Gets a value that determines whether or not this stream can be read. Always returns true.
        /// </summary>
        public override bool CanRead { get { return true; } }
        /// <summary>
        /// Gets a value that determines whether or not this stream supports seeking. Always returns false.
        /// </summary>
        public override bool CanSeek { get { return false; } }
        /// <summary>
        /// Gets a value that determines whether or not this stream can be written. Always returns false.
        /// </summary>
        public override bool CanWrite { get { return false; } }

        /// <summary>
        /// Reads the next chunk of data into <paramref name="buffer"/>.
        /// </summary>
        /// <param name="buffer">The buffer to read data to.</param>
        /// <param name="offset">Where in the buffer to read the data to.</param>
        /// <param name="count">How much data to read.</param>
        /// <returns></returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (string.IsNullOrEmpty(this.strHexa))
            {
                // Read the next chunk of data into the given buffer:
                long numRead = this.dr.GetBytes(this.ordinal, dataIndex: this.position, buffer: buffer, bufferIndex: offset, length: count);
                // Advance our tracked position:
                this.position += numRead;

                // Return how many bytes have been read (why this is a `long` escapes me, because the `length` parameter is an `int`):
                return (int)numRead;
            }
            else
            {
                int length = count * 2;
                if (count > this.strHexa.Length)
                    length = this.strHexa.Length;

                byte[] rb = Enumerable.Range(offset, length)
                             .Where(x => x % 2 == 0)
                             .Select(x => Convert.ToByte(this.strHexa.Substring(x, 2), 16))
                             .ToArray();
                rb.CopyTo(buffer, 0);
                this.position += rb.Length;
                return rb.Length;
            }
        }

        /// <summary>
        /// Gets the blob's length if provided otherwise throws an exception.
        /// </summary>
        public override long Length
        {
            get { if (!length.HasValue) throw new NotSupportedException(); return length.Value; }
        }

        /// <summary>
        /// Gets the current position in the data stream.
        /// </summary>
        public override long Position
        {
            get { return position; }
            set { throw new NotSupportedException(); }
        }

        #region Not supported

        /// <summary>
        /// Not supported.
        /// </summary>
        public override void Flush()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported.
        /// </summary>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported.
        /// </summary>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported.
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    public enum NAVObjectType
    {
        TableData,
        Table,
        NotDefinition1,
        Report,
        NotDefinition2,
        Codeunit,
        XMLport,
        MenuSuite,
        Page,
        Query,
        NotDefinition3,
        System,
        FieldNumber
    }
}
